/******************************************************************************* * Copyright (c) 2002, 2015 GEBIT Gesellschaft fuer EDV-Beratung * und Informatik-Technologien mbH, * Berlin, Duesseldorf, Frankfurt (Germany) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * GEBIT Gesellschaft fuer EDV-Beratung und Informatik-Technologien mbH - initial API and implementation * IBM Corporation - bug fixes * John-Mason P. Shackelford (john-mason.shackelford@pearson.com) - bug 49383, 56299, 59024 * Brock Janiczak (brockj_eclipse@ihug.com.au ) - bug 78028, 78030 * Remy Chi Jian Suen - bug 277587 *******************************************************************************/ package org.eclipse.ant.internal.ui.editor; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.tools.ant.AntTypeDefinition; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.ComponentHelper; import org.apache.tools.ant.ExtensionPoint; import org.apache.tools.ant.IntrospectionHelper; import org.apache.tools.ant.Project; import org.apache.tools.ant.Target; import org.apache.tools.ant.taskdefs.MacroDef; import org.apache.tools.ant.taskdefs.MacroDef.Attribute; import org.apache.tools.ant.taskdefs.MacroDef.TemplateElement; import org.apache.tools.ant.taskdefs.MacroInstance; import org.apache.tools.ant.types.EnumeratedAttribute; import org.apache.tools.ant.types.Reference; import org.eclipse.ant.internal.core.IAntCoreConstants; import org.eclipse.ant.internal.ui.AntUIImages; import org.eclipse.ant.internal.ui.AntUIPlugin; import org.eclipse.ant.internal.ui.IAntUIConstants; import org.eclipse.ant.internal.ui.dtd.IAttribute; import org.eclipse.ant.internal.ui.dtd.IDfm; import org.eclipse.ant.internal.ui.dtd.IElement; import org.eclipse.ant.internal.ui.dtd.ISchema; import org.eclipse.ant.internal.ui.dtd.ParseError; import org.eclipse.ant.internal.ui.dtd.Parser; import org.eclipse.ant.internal.ui.editor.TaskDescriptionProvider.ProposalNode; import org.eclipse.ant.internal.ui.editor.templates.AntContext; import org.eclipse.ant.internal.ui.editor.templates.AntTemplateAccess; import org.eclipse.ant.internal.ui.editor.templates.AntTemplateInformationControlCreator; import org.eclipse.ant.internal.ui.editor.templates.AntTemplateProposal; import org.eclipse.ant.internal.ui.editor.templates.BuildFileContextType; import org.eclipse.ant.internal.ui.editor.templates.TargetContextType; import org.eclipse.ant.internal.ui.editor.templates.TaskContextType; import org.eclipse.ant.internal.ui.model.AntDefiningTaskNode; import org.eclipse.ant.internal.ui.model.AntElementNode; import org.eclipse.ant.internal.ui.model.AntModel; import org.eclipse.ant.internal.ui.model.AntProjectNode; import org.eclipse.ant.internal.ui.model.AntTargetNode; import org.eclipse.ant.internal.ui.model.AntTaskNode; import org.eclipse.ant.internal.ui.model.IAntElement; import org.eclipse.core.commands.Command; import org.eclipse.core.commands.ParameterizedCommand; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.bindings.TriggerSequence; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.contentassist.ContentAssistEvent; import org.eclipse.jface.text.contentassist.ICompletionListener; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.IContentAssistant; import org.eclipse.jface.text.contentassist.IContentAssistantExtension2; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.jface.text.contentassist.IContextInformationValidator; import org.eclipse.jface.text.templates.Template; import org.eclipse.jface.text.templates.TemplateCompletionProcessor; import org.eclipse.jface.text.templates.TemplateContext; import org.eclipse.jface.text.templates.TemplateContextType; import org.eclipse.jface.text.templates.TemplateException; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.commands.ICommandService; import org.eclipse.ui.keys.IBindingService; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.progress.IProgressService; import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.ibm.icu.text.MessageFormat; /** * The completion processor for the Ant Editor. */ public class AntEditorCompletionProcessor extends TemplateCompletionProcessor implements ICompletionListener { private Comparator<ICompletionProposal> proposalComparator = new Comparator<ICompletionProposal>() { @Override public int compare(ICompletionProposal o1, ICompletionProposal o2) { int type1 = getProposalType(o1); int type2 = getProposalType(o2); if (type1 != type2) { if (type1 > type2) { return 1; } return -1; } String string1 = o1.getDisplayString(); String string2 = o2.getDisplayString(); return string1.compareToIgnoreCase(string2); } private int getProposalType(Object o) { if (o instanceof AntCompletionProposal) { return ((AntCompletionProposal) o).getType(); } return AntCompletionProposal.TASK_PROPOSAL; } }; protected final static int PROPOSAL_MODE_NONE = 0; protected final static int PROPOSAL_MODE_BUILDFILE = 1; protected final static int PROPOSAL_MODE_TASK_PROPOSAL = 2; protected final static int PROPOSAL_MODE_PROPERTY_PROPOSAL = 3; protected final static int PROPOSAL_MODE_ATTRIBUTE_PROPOSAL = 4; protected final static int PROPOSAL_MODE_TASK_PROPOSAL_CLOSING = 5; protected final static int PROPOSAL_MODE_ATTRIBUTE_VALUE_PROPOSAL = 6; protected final static int PROPOSAL_MODE_NESTED_ELEMENT_PROPOSAL = 7; private final static ICompletionProposal[] NO_PROPOSALS = new ICompletionProposal[0]; /** * The fully qualified name of the {@link MacroInstance} class * * @since 3.5.500 */ private static final String MACROINSTANCE_NAME = "org.apache.tools.ant.taskdefs.MacroInstance"; //$NON-NLS-1$ /** * The line where the cursor sits now. * <P> * The first line has index '1'. */ protected int lineNumber = -1; /** * The startingColumn where the cursor sits now. * <P> * The first startingColumn has index '1'. */ protected int columnNumber = -1; /** * The additional offset required from a required attribute to place the cursor for the current proposal */ private int additionalProposalOffset = -1; private static final String ANT_DTD_FILENAME = "/org/eclipse/ant/internal/ui/editor/ant1.6.2.dtd"; //$NON-NLS-1$ /** * The DTD. */ private static ISchema fgDtd; /** * Cursor position, counted from the beginning of the document. * <P> * The first position has index '0'. */ protected int cursorPosition = -1; /** * The text viewer. */ private ITextViewer viewer; /** * The set of characters that will trigger the activation of the completion proposal computation. */ private char[] autoActivationChars = null; private String errorMessage; protected AntModel antModel; /** * The proposal mode for the current content assist * * @see #determineProposalMode(IDocument, int, String) */ private int currentProposalMode = -1; /** * The prefix for the current content assist */ protected String currentPrefix = null; /** * The current task string for content assist * * @see #determineProposalMode(IDocument, int, String) */ protected String currentTaskString = null; private boolean fTemplatesOnly = false; protected IContentAssistantExtension2 fContentAssistant; public AntEditorCompletionProcessor(AntModel model) { super(); antModel = model; } /** * Parses the dtd. */ private ISchema parseDtd() throws ParseError, IOException { try (InputStream stream = getClass().getResourceAsStream(ANT_DTD_FILENAME); Reader reader = new InputStreamReader(stream, "UTF-8");) {//$NON-NLS-1$ Parser parser = new Parser(); ISchema schema = parser.parseDTD(reader, "project"); //$NON-NLS-1$ return schema; } } /* * (non-Javadoc) * * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeCompletionProposals(org.eclipse.jface.text.ITextViewer, int) */ @Override public ICompletionProposal[] computeCompletionProposals(ITextViewer refViewer, int documentOffset) { this.viewer = refViewer; try { if (fTemplatesOnly) { fContentAssistant.setStatusMessage(getIterationGestureMessage(AntEditorMessages.getString("AntEditorCompletionProcessor.0"))); //$NON-NLS-1$ fContentAssistant.setEmptyMessage(AntEditorMessages.getString("AntEditorCompletionProcessor.60")); //$NON-NLS-1$ ICompletionProposal[] templates = determineTemplateProposals(refViewer, documentOffset); Arrays.sort(templates, proposalComparator); return templates; } fContentAssistant.setStatusMessage(getIterationGestureMessage(AntEditorMessages.getString("AntEditorCompletionProcessor.61"))); //$NON-NLS-1$ fContentAssistant.setEmptyMessage(AntEditorMessages.getString("AntEditorCompletionProcessor.62")); //$NON-NLS-1$ ICompletionProposal[] matchingProposals = determineProposals(); ICompletionProposal[] matchingTemplateProposals = determineTemplateProposals(refViewer, documentOffset); return mergeProposals(matchingProposals, matchingTemplateProposals); } finally { currentPrefix = null; currentProposalMode = -1; fTemplatesOnly = !fTemplatesOnly; } } protected ICompletionProposal[] determineTemplateProposals(ITextViewer refViewer, int documentOffset) { this.viewer = refViewer; String prefix = getCurrentPrefix(); ICompletionProposal[] matchingTemplateProposals; if (prefix.length() == 0) { matchingTemplateProposals = determineTemplateProposalsForContext(documentOffset); } else { ICompletionProposal[] templateProposals = determineTemplateProposalsForContext(documentOffset); List<ICompletionProposal> templateProposalList = new ArrayList<>(templateProposals.length); for (int i = 0; i < templateProposals.length; i++) { if (templateProposals[i].getDisplayString().toLowerCase().startsWith(prefix)) { templateProposalList.add(templateProposals[i]); } } matchingTemplateProposals = templateProposalList.toArray(new ICompletionProposal[templateProposalList.size()]); } return matchingTemplateProposals; } // essentially a copy of super.computeCompletionProposals but we need to have both context types work // for target (task and target) context type in a backwards compatible way private ICompletionProposal[] determineTemplateProposalsForContext(int offset) { ITextSelection selection = (ITextSelection) viewer.getSelectionProvider().getSelection(); // adjust offset to end of normalized selection int newoffset = offset; if (selection.getOffset() == newoffset) { newoffset = selection.getOffset() + selection.getLength(); } String prefix = extractPrefix(viewer, newoffset); Region region = new Region(newoffset - prefix.length(), prefix.length()); TemplateContext context = createContext(viewer, region); if (context == null) { return new ICompletionProposal[0]; } context.setVariable("selection", selection.getText()); // name of the selection variables {line, word}_selection //$NON-NLS-1$ Template[] templates; String contextTypeId = context.getContextType().getId(); boolean isTargetContextType = contextTypeId.equals(TargetContextType.TARGET_CONTEXT_TYPE); if (isTargetContextType) { Template[] tasks = AntTemplateAccess.getDefault().getTemplateStore().getTemplates(TaskContextType.TASK_CONTEXT_TYPE); Template[] targets = getTemplates(contextTypeId); templates = new Template[tasks.length + targets.length]; System.arraycopy(tasks, 0, templates, 0, tasks.length); System.arraycopy(targets, 0, templates, tasks.length, targets.length); } else { templates = getTemplates(contextTypeId); } List<ICompletionProposal> matches = new ArrayList<>(); for (int i = 0; i < templates.length; i++) { Template template = templates[i]; try { context.getContextType().validate(template.getPattern()); } catch (TemplateException e) { continue; } if (template.matches(prefix, contextTypeId) || (isTargetContextType && template.matches(prefix, TaskContextType.TASK_CONTEXT_TYPE))) { matches.add(createProposal(template, context, (IRegion) region, getRelevance(template, prefix))); } } Collections.sort(matches, proposalComparator); return matches.toArray(new ICompletionProposal[matches.size()]); } private ICompletionProposal[] mergeProposals(ICompletionProposal[] proposals1, ICompletionProposal[] proposals2) { ICompletionProposal[] combinedProposals = new ICompletionProposal[proposals1.length + proposals2.length]; System.arraycopy(proposals1, 0, combinedProposals, 0, proposals1.length); System.arraycopy(proposals2, 0, combinedProposals, proposals1.length, proposals2.length); Arrays.sort(combinedProposals, proposalComparator); return combinedProposals; } /** * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#computeContextInformation(ITextViewer, int) */ @Override public IContextInformation[] computeContextInformation(ITextViewer refViewer, int documentOffset) { return new IContextInformation[0]; } /** * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getCompletionProposalAutoActivationCharacters() */ @Override public char[] getCompletionProposalAutoActivationCharacters() { return autoActivationChars; } /** * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationAutoActivationCharacters() */ @Override public char[] getContextInformationAutoActivationCharacters() { return null; } /** * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getContextInformationValidator() */ @Override public IContextInformationValidator getContextInformationValidator() { return null; } /** * @see org.eclipse.jface.text.contentassist.IContentAssistProcessor#getErrorMessage() */ @Override public String getErrorMessage() { return errorMessage; } /** * Returns the new determined proposals. */ private ICompletionProposal[] determineProposals() { ITextSelection selection = (ITextSelection) viewer.getSelectionProvider().getSelection(); cursorPosition = selection.getOffset() + selection.getLength(); IDocument doc = viewer.getDocument(); try { lineNumber = doc.getLineOfOffset(cursorPosition); columnNumber = cursorPosition - doc.getLineOffset(lineNumber); } catch (BadLocationException e) { AntUIPlugin.log(e); } String prefix = getCurrentPrefix(); if (prefix == null || cursorPosition == -1) { return NO_PROPOSALS; } ICompletionProposal[] proposals = getProposalsFromDocument(doc, prefix); currentTaskString = null; return proposals; } /** * Returns the proposals for the specified document. */ protected ICompletionProposal[] getProposalsFromDocument(IDocument document, String prefix) { ICompletionProposal[] proposals = null; currentProposalMode = determineProposalMode(document, cursorPosition, prefix); switch (currentProposalMode) { case PROPOSAL_MODE_ATTRIBUTE_PROPOSAL: proposals = getAttributeProposals(currentTaskString, prefix); if (proposals.length == 0) { errorMessage = AntEditorMessages.getString("AntEditorCompletionProcessor.28"); //$NON-NLS-1$ } break; case PROPOSAL_MODE_TASK_PROPOSAL: String parentName = getParentName(document, lineNumber, columnNumber); if (parentName == null || parentName.length() == 0) { // outside of any parent element proposals = NO_PROPOSALS; currentProposalMode = PROPOSAL_MODE_NONE; } else { proposals = getTaskProposals(document, parentName, prefix); } if (proposals.length == 0) { errorMessage = AntEditorMessages.getString("AntEditorCompletionProcessor.29"); //$NON-NLS-1$ } break; case PROPOSAL_MODE_BUILDFILE: proposals = getBuildFileProposals(document, prefix); if (proposals.length == 0) { errorMessage = AntEditorMessages.getString("AntEditorCompletionProcessor.29"); //$NON-NLS-1$ } break; case PROPOSAL_MODE_TASK_PROPOSAL_CLOSING: ICompletionProposal proposal = getClosingTaskProposal(getOpenElementName(), prefix, true); if (proposal == null) { errorMessage = AntEditorMessages.getString("AntEditorCompletionProcessor.30"); //$NON-NLS-1$ proposals = NO_PROPOSALS; } else { proposals = new ICompletionProposal[] { proposal }; } break; case PROPOSAL_MODE_ATTRIBUTE_VALUE_PROPOSAL: String textToSearch = document.get().substring(0, cursorPosition - prefix.length()); String attributeString = getAttributeStringFromDocumentStringToPrefix(textToSearch); if ("target".equalsIgnoreCase(currentTaskString) || "extension-point".equalsIgnoreCase(currentTaskString)) { //$NON-NLS-1$ //$NON-NLS-2$ proposals = getTargetAttributeValueProposals(document, textToSearch, prefix, attributeString); } else if ("antcall".equalsIgnoreCase(currentTaskString)) { //$NON-NLS-1$ proposals = getAntCallAttributeValueProposals(document, prefix, attributeString); } else if ("project".equalsIgnoreCase(currentTaskString)) { //$NON-NLS-1$ proposals = getProjectAttributeValueProposals(prefix, attributeString); } else if ("refid".equalsIgnoreCase(attributeString) || "classpathref".equalsIgnoreCase(attributeString) //$NON-NLS-1$//$NON-NLS-2$ || "sourcepathref".equalsIgnoreCase(attributeString) || "bootpathref".equalsIgnoreCase(attributeString)) { //$NON-NLS-1$ //$NON-NLS-2$ proposals = getReferencesValueProposals(prefix); } else { proposals = getAttributeValueProposals(currentTaskString, attributeString, prefix); } if (proposals.length == 0) { errorMessage = AntEditorMessages.getString("AntEditorCompletionProcessor.31"); //$NON-NLS-1$ } break; case PROPOSAL_MODE_PROPERTY_PROPOSAL: proposals = getPropertyProposals(document, prefix, cursorPosition); if (proposals.length == 0) { errorMessage = AntEditorMessages.getString("AntEditorCompletionProcessor.32"); //$NON-NLS-1$ } break; case PROPOSAL_MODE_NONE: default: proposals = NO_PROPOSALS; errorMessage = AntEditorMessages.getString("AntEditorCompletionProcessor.33"); //$NON-NLS-1$ } if (proposals.length > 0) { errorMessage = IAntCoreConstants.EMPTY_STRING; } return proposals; } private ICompletionProposal[] getProjectAttributeValueProposals(String prefix, String attributeName) { if (attributeName.equalsIgnoreCase(IAntCoreConstants.DEFAULT)) { return getDefaultValueProposals(prefix); } return NO_PROPOSALS; } private ICompletionProposal[] getDefaultValueProposals(String prefix) { Map<String, Target> targets = getTargets(); List<AntCompletionProposal> defaultProposals = new ArrayList<>(targets.size()); Iterator<Target> itr = targets.values().iterator(); Target target; String targetName; while (itr.hasNext()) { target = itr.next(); targetName = target.getName(); if (targetName.toLowerCase().startsWith(prefix) && targetName.length() > 0) { defaultProposals.add(new AntCompletionProposal(targetName, cursorPosition - prefix.length(), prefix.length(), targetName.length(), getTargetImage(targetName), targetName, target.getDescription(), AntCompletionProposal.TASK_PROPOSAL)); } } ICompletionProposal[] proposals = new ICompletionProposal[defaultProposals.size()]; return defaultProposals.toArray(proposals); } private ICompletionProposal[] getReferencesValueProposals(String prefix) { Project project = antModel.getProjectNode().getProject(); Map<String, Object> references = project.getReferences(); if (references.isEmpty()) { return NO_PROPOSALS; } IAntElement node = antModel.getNode(cursorPosition, false); if (node == null) { return NO_PROPOSALS; } while (node.getParentNode() instanceof AntTaskNode) { node = node.getParentNode(); } String id = null; if (node instanceof AntTaskNode) { id = ((AntTaskNode) node).getId(); } Set<String> refIds = references.keySet(); List<ICompletionProposal> proposals = new ArrayList<>(refIds.size()); String refId; ICompletionProposal proposal; int prefixLength = prefix.length(); int replacementOffset = cursorPosition - prefixLength; Iterator<String> iter = refIds.iterator(); while (iter.hasNext()) { refId = iter.next(); if (!refId.equals(id) && (prefixLength == 0 || refId.toLowerCase().startsWith(prefix))) { proposal = new AntCompletionProposal(refId, replacementOffset, prefixLength, refId.length(), null, refId, null, AntCompletionProposal.TASK_PROPOSAL); proposals.add(proposal); } } return proposals.toArray(new ICompletionProposal[proposals.size()]); } protected ICompletionProposal[] getTargetAttributeValueProposals(IDocument document, String textToSearch, String prefix, String attributeName) { if (attributeName.equalsIgnoreCase("depends")) { //$NON-NLS-1$ return getDependsValueProposals(document, prefix); } else if (attributeName.equalsIgnoreCase("if") || attributeName.equalsIgnoreCase("unless")) { //$NON-NLS-1$ //$NON-NLS-2$ if (!textToSearch.trim().endsWith(",")) { //$NON-NLS-1$ return getPropertyProposals(document, prefix, cursorPosition); } } else if (attributeName.equalsIgnoreCase("extensionOf")) {//$NON-NLS-1$ return getExtensionOfValueProposals(prefix); } return NO_PROPOSALS; } private ICompletionProposal[] getExtensionOfValueProposals(String prefix) { List<ICompletionProposal> extensions = new ArrayList<>(); Map<String, Target> targets = getTargets(); Set<String> targetNames = targets.keySet(); for (String targetName : targetNames) { Target currentTarget = targets.get(targetName); if (currentTarget instanceof ExtensionPoint) { extensions.add(new AntCompletionProposal(targetName, cursorPosition - prefix.length(), prefix.length(), targetName.length(), getTargetImage(targetName), targetName, targets.get(targetName).getDescription(), AntCompletionProposal.TASK_PROPOSAL)); } } return extensions.toArray(new ICompletionProposal[extensions.size()]); } protected ICompletionProposal[] getAntCallAttributeValueProposals(IDocument document, String prefix, String attributeName) { if (attributeName.equalsIgnoreCase("target")) { //$NON-NLS-1$ return getTargetProposals(document, prefix); } return NO_PROPOSALS; } private ICompletionProposal[] getTargetProposals(IDocument document, String prefix) { String currentTargetName = getEnclosingTargetName(document, lineNumber, columnNumber); if (currentTargetName == null) { return NO_PROPOSALS; } Map<String, Target> targets = getTargets(); Set<String> targetNames = targets.keySet(); List<ICompletionProposal> proposals = new ArrayList<>(targets.size() - 2); // current target and implicit target for (String targetName : targetNames) { if (targetName.equals(currentTargetName)) { continue; } if (targetName.toLowerCase().startsWith(prefix) && targetName.length() > 0) { proposals.add(new AntCompletionProposal(targetName, cursorPosition - prefix.length(), prefix.length(), targetName.length(), getTargetImage(targetName), targetName, targets.get(targetName).getDescription(), AntCompletionProposal.TASK_PROPOSAL)); } } return proposals.toArray(new ICompletionProposal[proposals.size()]); } /** * Retrieves the representative image of a target of the given name. If the target cannot be found, <code>null</code> will be returned. * * @param targetName * the target's name * @return an image suitable for representing the target, or <code>null</code> if the target cannot be found * @since 3.6 */ private Image getTargetImage(String targetName) { AntTargetNode targetNode = antModel.getTargetNode(targetName); if (targetNode == null) { return null; } else if (targetNode.isInternal()) { return AntUIImages.getImage(IAntUIConstants.IMG_ANT_TARGET_INTERNAL); } else if (targetNode.isDefaultTarget()) { return AntUIImages.getImage(IAntUIConstants.IMG_ANT_DEFAULT_TARGET); } else { return AntUIImages.getImage(IAntUIConstants.IMG_ANT_TARGET); } } private ICompletionProposal[] getDependsValueProposals(IDocument document, String prefix) { List<String> possibleDependencies = new ArrayList<>(); String currentTargetName = getEnclosingTargetName(document, lineNumber, columnNumber); if (currentTargetName == null) { return NO_PROPOSALS; } Map<String, Target> targets = getTargets(); Set<String> targetNames = targets.keySet(); Enumeration<String> dependencies = null; for (String targetName : targetNames) { if (targetName.equals(currentTargetName)) { Target currentTarget = targets.get(targetName); dependencies = currentTarget.getDependencies(); continue; } if (targetName.toLowerCase().startsWith(prefix) && targetName.length() > 0) { possibleDependencies.add(targetName); } } if (dependencies != null) { while (dependencies.hasMoreElements()) { possibleDependencies.remove(dependencies.nextElement()); } } ArrayList<ICompletionProposal> proposals = new ArrayList<>(possibleDependencies.size()); for (String targetName : possibleDependencies) { ICompletionProposal proposal = new AntCompletionProposal(targetName, cursorPosition - prefix.length(), prefix.length(), targetName.length(), getTargetImage(targetName), targetName, targets.get(targetName).getDescription(), AntCompletionProposal.TASK_PROPOSAL); proposals.add(proposal); } return proposals.toArray(new ICompletionProposal[proposals.size()]); } /** * Returns all possible attributes for the specified task. * * @param taskName * the name of the task for that the attribute shall be completed * @param prefix * prefix, that all proposals should start with. The prefix may be an empty string. */ protected ICompletionProposal[] getAttributeProposals(String taskName, String prefix) { List<ICompletionProposal> proposals = new ArrayList<>(); IElement element = getDtd().getElement(taskName); if (element != null) { Iterator<String> keys = element.getAttributes().keySet().iterator(); while (keys.hasNext()) { String attrName = keys.next(); if (prefix.length() == 0 || attrName.toLowerCase().startsWith(prefix)) { IAttribute dtdAttributes = element.getAttributes().get(attrName); String replacementString = attrName + "=\"\""; //$NON-NLS-1$ String displayString = attrName; String[] items = dtdAttributes.getEnum(); if (items != null) { if (items.length > 1) { displayString += " - ("; //$NON-NLS-1$ } for (int i = 0; i < items.length; i++) { displayString += items[i]; if (i + 1 < items.length) { displayString += " | "; //$NON-NLS-1$ } else { displayString += ")"; //$NON-NLS-1$ } } } addAttributeProposal(taskName, prefix, proposals, attrName, replacementString, displayString, true); } } } else { // possibly a user defined task or type AntTypeDefinition taskClass = getTaskClass(taskName); if (taskClass != null) { if (MACROINSTANCE_NAME.equals(taskClass.getClassName())) { addMacroDefAttributeProposals(taskName, prefix, proposals); } else { IntrospectionHelper helper = getIntrospectionHelper(taskClass); if (helper != null) { addAttributeProposals(helper, taskName, prefix, proposals); } } } else { // nested user defined element AntTypeDefinition nestedType = getNestedType(); if (nestedType != null) { IntrospectionHelper helper = getIntrospectionHelper(nestedType); if (helper != null) { addAttributeProposals(helper, taskName, prefix, proposals); } } } } return proposals.toArray(new ICompletionProposal[proposals.size()]); } private void addAttributeProposals(IntrospectionHelper helper, String taskName, String prefix, List<ICompletionProposal> proposals) { Enumeration<String> attributes = helper.getAttributes(); while (attributes.hasMoreElements()) { String attribute = attributes.nextElement(); if (prefix.length() == 0 || attribute.toLowerCase().startsWith(prefix)) { String replacementString = attribute + "=\"\""; //$NON-NLS-1$ addAttributeProposal(taskName, prefix, proposals, attribute, replacementString, attribute, false); } } } private AntTypeDefinition getNestedType() { AntElementNode currentNode = antModel.getNode(cursorPosition, false); if (currentNode == null) { return null; } IAntElement parent = currentNode.getParentNode(); if (parent instanceof AntTaskNode) { String parentName = parent.getName(); if (hasNestedElements(parentName)) { AntTypeDefinition taskClass = getTaskClass(parentName); if (taskClass != null) { IntrospectionHelper helper = getIntrospectionHelper(taskClass); if (helper != null) { try { Class<?> nestedType = helper.getElementType(currentNode.getName()); // TODO probably a much better way to find the definition of a nested type than this AntTypeDefinition def = new AntTypeDefinition(); def.setClass(nestedType); def.setClassName(nestedType.getName()); return def; } catch (BuildException be) { // do nothing } } } } } return null; } private IntrospectionHelper getIntrospectionHelper(AntTypeDefinition taskClass) { Project p = antModel.getProjectNode().getProject(); Class<?> clazz = taskClass.getExposedClass(p); if (clazz != null) { IntrospectionHelper helper = null; try { helper = IntrospectionHelper.getHelper(antModel.getProjectNode().getProject(), clazz); } catch (NoClassDefFoundError e) { // ignore as a task may require additional classpath components } return helper; } return null; } private void addMacroDefAttributeProposals(String taskName, String prefix, List<ICompletionProposal> proposals) { currentProposalMode = PROPOSAL_MODE_ATTRIBUTE_PROPOSAL; AntDefiningTaskNode node = antModel.getDefininingTaskNode(taskName); Object task = node.getRealTask(); if (!(task instanceof MacroDef)) { return; } List<Attribute> attributes = ((MacroDef) task).getAttributes(); Iterator<Attribute> itr = attributes.iterator(); while (itr.hasNext()) { MacroDef.Attribute attribute = itr.next(); String attributeName = attribute.getName(); if (!(prefix.length() == 0 || attributeName.toLowerCase().startsWith(prefix))) { continue; } String replacementString = attributeName + "=\"\""; //$NON-NLS-1$ String proposalInfo = null; String description = attribute.getDescription(); if (description != null) { proposalInfo = description; } String deflt = attribute.getDefault(); if (deflt != null && deflt.length() > 0) { proposalInfo = (proposalInfo == null ? "<BR><BR>" : (proposalInfo += "<BR><BR>")); //$NON-NLS-1$ //$NON-NLS-2$ proposalInfo += MessageFormat.format(AntEditorMessages.getString("AntEditorCompletionProcessor.59"), new Object[] { deflt }); //$NON-NLS-1$ } ICompletionProposal proposal = new AntCompletionProposal(replacementString, cursorPosition - prefix.length(), prefix.length(), attributeName.length() + 2, null, attributeName, proposalInfo, AntCompletionProposal.TASK_PROPOSAL); proposals.add(proposal); } } private void addMacroDefElementProposals(String taskName, String prefix, List<ICompletionProposal> proposals) { AntDefiningTaskNode node = antModel.getDefininingTaskNode(taskName); Object task = node.getRealTask(); if (!(task instanceof MacroDef)) { return; } Map<String, TemplateElement> elements = ((MacroDef) task).getElements(); Iterator<String> itr = elements.keySet().iterator(); int prefixLength = prefix.length(); int replacementOffset = cursorPosition - prefixLength; while (itr.hasNext()) { String elementName = itr.next(); if (!(prefixLength == 0 || elementName.toLowerCase().startsWith(prefix))) { continue; } MacroDef.TemplateElement element = elements.get(elementName); String replacementString = MessageFormat.format("<{0}>\n</{1}>", new Object[] { elementName, elementName }); //$NON-NLS-1$ String proposalInfo = null; String description = element.getDescription(); if (description != null) { proposalInfo = description; } proposalInfo = (proposalInfo == null ? "<BR><BR>" : (proposalInfo += "<BR><BR>")); //$NON-NLS-1$ //$NON-NLS-2$ if (element.isOptional()) { proposalInfo += AntEditorMessages.getString("AntEditorCompletionProcessor.1"); //$NON-NLS-1$ } else { proposalInfo += AntEditorMessages.getString("AntEditorCompletionProcessor.2"); //$NON-NLS-1$ } ICompletionProposal proposal = new AntCompletionProposal(replacementString, replacementOffset, prefixLength, elementName.length() + 2, null, elementName, proposalInfo, AntCompletionProposal.TASK_PROPOSAL); proposals.add(proposal); } } private void addAttributeProposal(String taskName, String prefix, List<ICompletionProposal> proposals, String attrName, String replacementString, String displayString, boolean lookupDescription) { String proposalInfo = null; if (lookupDescription) { String required = getDescriptionProvider().getRequiredAttributeForTaskAttribute(taskName, attrName); if (required != null && required.length() > 0) { proposalInfo = AntEditorMessages.getString("AntEditorCompletionProcessor.Required___4") + required; //$NON-NLS-1$ proposalInfo += "<BR><BR>"; //$NON-NLS-1$ } String description = getDescriptionProvider().getDescriptionForTaskAttribute(taskName, attrName); if (description != null) { proposalInfo = (proposalInfo == null ? IAntCoreConstants.EMPTY_STRING : proposalInfo); proposalInfo += description; } } ICompletionProposal proposal = new AntCompletionProposal(replacementString, cursorPosition - prefix.length(), prefix.length(), attrName.length() + 2, null, displayString, proposalInfo, AntCompletionProposal.TASK_PROPOSAL); proposals.add(proposal); } /** * Returns all possible values for the specified attribute of the specified task. * * @param aTaskName * the name of the task that the specified attribute belongs to. * * @param anAttributeName * the name of the attribute for that the value shall be completed * * @param prefix * the prefix that all proposals should start with. The prefix may be an empty string. */ private ICompletionProposal[] getAttributeValueProposals(String taskName, String attributeName, String prefix) { List<ICompletionProposal> proposals = new ArrayList<>(); IElement taskElement = getDtd().getElement(taskName); if (taskElement != null) { IAttribute attribute = taskElement.getAttributes().get(attributeName); if (attribute != null) { String[] items = attribute.getEnum(); if (items != null) { String item; for (int i = 0; i < items.length; i++) { item = items[i]; if (prefix.length() == 0 || item.toLowerCase().startsWith(prefix)) { proposals.add(new AntCompletionProposal(item, cursorPosition - prefix.length(), prefix.length(), item.length(), null, item, null, AntCompletionProposal.TASK_PROPOSAL)); } } } } } else { // possibly a user defined task or type AntTypeDefinition taskClass = getTaskClass(taskName); if (taskClass != null) { IntrospectionHelper helper = getIntrospectionHelper(taskClass); if (helper != null) { addAttributeValueProposals(helper, attributeName, prefix, proposals); } } else { // nested user defined element AntTypeDefinition nestedType = getNestedType(); if (nestedType != null) { IntrospectionHelper helper = getIntrospectionHelper(nestedType); if (helper != null) { addAttributeValueProposals(helper, attributeName, prefix, proposals); } } } } return proposals.toArray(new ICompletionProposal[proposals.size()]); } private void addAttributeValueProposals(IntrospectionHelper helper, String attributeName, String prefix, List<ICompletionProposal> proposals) { Enumeration<String> attributes = helper.getAttributes(); while (attributes.hasMoreElements()) { String attribute = attributes.nextElement(); if (attribute.equals(attributeName)) { Class<?> attributeType = helper.getAttributeType(attribute); addAttributeValueProposalsForAttributeType(attributeType, prefix, proposals); break; } } } private void addAttributeValueProposalsForAttributeType(Class<?> attributeType, String prefix, List<ICompletionProposal> proposals) { if ((attributeType == Boolean.TYPE || attributeType == Boolean.class) && prefix.length() <= 5) { addBooleanAttributeValueProposals(prefix, proposals); } else if (EnumeratedAttribute.class.isAssignableFrom(attributeType)) { try { addEnumeratedAttributeValueProposals(attributeType, prefix, proposals); } catch (InstantiationException e) { // do nothing } catch (IllegalAccessException e) { // do nothing } } else if (Reference.class == attributeType) { proposals.addAll(Arrays.asList(getReferencesValueProposals(prefix))); } } private void addEnumeratedAttributeValueProposals(Class<?> type, String prefix, List<ICompletionProposal> proposals) throws InstantiationException, IllegalAccessException { EnumeratedAttribute ea = (EnumeratedAttribute) type.newInstance(); String[] values = ea.getValues(); String enumerated; for (int i = 0; i < values.length; i++) { enumerated = values[i].toLowerCase(); if (prefix.length() == 0 || enumerated.startsWith(prefix)) { proposals.add(new AntCompletionProposal(enumerated, cursorPosition - prefix.length(), prefix.length(), enumerated.length(), null, enumerated, null, AntCompletionProposal.TASK_PROPOSAL)); } } } private void addBooleanAttributeValueProposals(String prefix, List<ICompletionProposal> proposals) { String[] booleanValues = new String[] { "true", "false", "on", "off", "yes", "no" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ String booleanAssist; for (int i = 0; i < booleanValues.length; i++) { booleanAssist = booleanValues[i].toLowerCase(); if (prefix.length() == 0 || booleanAssist.startsWith(prefix)) { proposals.add(new AntCompletionProposal(booleanAssist, cursorPosition - prefix.length(), prefix.length(), booleanAssist.length(), null, booleanAssist, null, AntCompletionProposal.TASK_PROPOSAL)); } } } /** * Returns all possible properties for the specified prefix. * <P> * Note that the completion mode must be property mode, otherwise it is not safe to call this method. */ protected ICompletionProposal[] getPropertyProposals(IDocument document, String prefix, int aCursorPosition) { List<ICompletionProposal> proposals = new ArrayList<>(); Map<String, ICompletionProposal> displayStringToProposals = new HashMap<>(); Map<String, Object> properties = findPropertiesFromDocument(); Image image = AntUIImages.getImage(IAntUIConstants.IMG_PROPERTY); // Determine replacement length and offset // String from beginning to the beginning of the prefix int replacementLength = prefix.length(); int replacementOffset = 0; String text = document.get(); String stringToPrefix = text.substring(0, aCursorPosition - prefix.length()); // Property proposal String lastTwoCharacters = stringToPrefix.substring(stringToPrefix.length() - 2, stringToPrefix.length()); boolean appendBraces = true; if (lastTwoCharacters.equals("${")) { //$NON-NLS-1$ replacementLength += 2; replacementOffset = aCursorPosition - prefix.length() - 2; } else if (lastTwoCharacters.endsWith("$")) { //$NON-NLS-1$ replacementLength += 1; replacementOffset = aCursorPosition - prefix.length() - 1; } else { // support for property proposals for the if/unless attributes of targets replacementOffset = aCursorPosition - prefix.length(); appendBraces = false; } if (text.length() > aCursorPosition && text.charAt(aCursorPosition) == '}') { replacementLength += 1; } String propertyName; for (Iterator<String> i = properties.keySet().iterator(); i.hasNext();) { propertyName = i.next(); if (prefix.length() == 0 || propertyName.toLowerCase().startsWith(prefix)) { String additionalPropertyInfo = (String) properties.get(propertyName); StringBuffer replacementString = new StringBuffer(); if (appendBraces) { replacementString.append("${"); //$NON-NLS-1$ } replacementString.append(propertyName); if (appendBraces) { replacementString.append('}'); } if (displayStringToProposals.get(propertyName) == null) { ICompletionProposal proposal = new AntCompletionProposal(replacementString.toString(), replacementOffset, replacementLength, replacementString.length(), image, propertyName, additionalPropertyInfo, AntCompletionProposal.PROPERTY_PROPOSAL); proposals.add(proposal); displayStringToProposals.put(propertyName, proposal); } } } return proposals.toArray(new ICompletionProposal[proposals.size()]); } /** * Returns all possible proposals for the specified parent name. * <P> * No completions will be returned if <code>parentName</code> is not known. * * @param document * the entire document * @param parentName * name of the parent (surrounding) element. * @param prefix * the prefix that all proposals should start with. The prefix may be an empty string. */ protected ICompletionProposal[] getTaskProposals(IDocument document, String parentName, String prefix) { List<ICompletionProposal> proposals = new ArrayList<>(250); ICompletionProposal proposal; if (areTasksOrTypesValidChildren(parentName)) { // use the definitions in the project as that includes more than what is defined in the DTD Project project = antModel.getProjectNode().getProject(); Map<String, AntTypeDefinition> tasksAndTypes = ComponentHelper.getComponentHelper(project).getAntTypeTable(); createProposals(document, prefix, proposals, tasksAndTypes); if (parentName.equals("project")) { //$NON-NLS-1$ if ("target".startsWith(prefix)) { //$NON-NLS-1$ proposals.add(newCompletionProposal(document, prefix, "target")); //$NON-NLS-1$ } if ("extension-point".startsWith(prefix)) { //$NON-NLS-1$ proposals.add(newCompletionProposal(document, prefix, "extension-point")); //$NON-NLS-1$ } } } else { IElement parent = getDtd().getElement(parentName); if (parent != null) { IDfm dfm = parent.getDfm(); String[] accepts = dfm.getAccepts(); if (accepts.length == 0) { currentProposalMode = PROPOSAL_MODE_NONE; } String elementName; for (int i = 0; i < accepts.length; i++) { elementName = accepts[i]; if (prefix.length() == 0 || elementName.toLowerCase().startsWith(prefix)) { proposal = newCompletionProposal(document, prefix, elementName); proposals.add(proposal); } } } else { // a nested element of a user defined task/type? AntTypeDefinition taskClass = getTaskClass(parentName); if (taskClass != null) { if (MACROINSTANCE_NAME.equals(taskClass.getClassName())) { currentProposalMode = PROPOSAL_MODE_ATTRIBUTE_PROPOSAL; addMacroDefElementProposals(parentName, prefix, proposals); } else { currentProposalMode = PROPOSAL_MODE_NESTED_ELEMENT_PROPOSAL; IntrospectionHelper helper = getIntrospectionHelper(taskClass); if (helper != null) { Enumeration<String> nested = helper.getNestedElements(); String nestedElement; while (nested.hasMoreElements()) { nestedElement = nested.nextElement(); if (prefix.length() == 0 || nestedElement.toLowerCase().startsWith(prefix)) { proposal = newCompletionProposal(document, prefix, nestedElement); proposals.add(proposal); } } } } } } } proposal = getClosingTaskProposal(getOpenElementName(), prefix, false); if (proposal != null) { proposals.add(proposal); } return proposals.toArray(new ICompletionProposal[proposals.size()]); } private boolean areTasksOrTypesValidChildren(String parentName) { return parentName == "project" || parentName == "target" || parentName == "sequential" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ || parentName == "presetdef" || parentName == "parallel" || parentName == "daemons" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ || parentName == "extension-point"; //$NON-NLS-1$ } /** * Returns proposals that define the structure of a build file. * * Note that template proposals which define the structure of a build file are handled by {@link #determineTemplateProposals(ITextViewer, int)} * which limits proposals by context type. * * @param document * the entire document * @param prefix * the prefix that all proposals should start with. The prefix may be an empty string. */ protected ICompletionProposal[] getBuildFileProposals(IDocument document, String prefix) { String rootElementName = "project"; //$NON-NLS-1$ IElement rootElement = getDtd().getElement(rootElementName); if (rootElement != null && rootElementName.toLowerCase().startsWith(prefix)) { ICompletionProposal proposal = newCompletionProposal(document, prefix, rootElementName); return new ICompletionProposal[] { proposal }; } return NO_PROPOSALS; } private void createProposals(IDocument document, String prefix, List<ICompletionProposal> proposals, Map<String, AntTypeDefinition> tasks) { Iterator<String> keys = tasks.keySet().iterator(); ICompletionProposal proposal; String key; while (keys.hasNext()) { key = antModel.getUserNamespaceCorrectName(keys.next()); if (prefix.length() == 0 || key.toLowerCase().startsWith(prefix)) { proposal = newCompletionProposal(document, prefix, key); proposals.add(proposal); } } } private ICompletionProposal newCompletionProposal(IDocument document, String aPrefix, String elementName) { additionalProposalOffset = 0; Image proposalImage = AntUIImages.getImage(IAntUIConstants.IMG_TASK_PROPOSAL); String proposalInfo = getDescriptionProvider().getDescriptionForTask(elementName); boolean hasNestedElements = hasNestedElements(elementName); String replacementString = getTaskProposalReplacementString(elementName, hasNestedElements); int replacementOffset = cursorPosition - aPrefix.length(); int replacementLength = aPrefix.length(); if (replacementOffset > 0 && document.get().charAt(replacementOffset - 1) == '<') { replacementOffset--; replacementLength++; } int proposalCursorPosition; if (hasNestedElements) { proposalCursorPosition = elementName.length() + 2 + additionalProposalOffset; } else { if (additionalProposalOffset > 0) { additionalProposalOffset += 2; // <antstructure output="|"/> } else { additionalProposalOffset += 1; // <arg|/> } proposalCursorPosition = elementName.length() + additionalProposalOffset; } return new AntCompletionProposal(replacementString, replacementOffset, replacementLength, proposalCursorPosition, proposalImage, elementName, proposalInfo, AntCompletionProposal.TASK_PROPOSAL); } /** * Returns the one possible completion for the specified unclosed task . * * @param openElementName * the task that hasn't been closed last * @param prefix * The prefix that the one possible proposal should start with. The prefix may be an empty string. * @return the proposal or <code>null</code> if no closing proposal available */ private ICompletionProposal getClosingTaskProposal(String openElementName, String prefix, boolean closingMode) { char previousChar = getPreviousChar(); ICompletionProposal proposal = null; if (openElementName != null) { if (prefix.length() == 0 || openElementName.toLowerCase().startsWith(prefix)) { StringBuffer replaceString = new StringBuffer(); if (!closingMode) { if (previousChar != '/') { if (previousChar != '<') { replaceString.append('<'); } replaceString.append('/'); } } replaceString.append(openElementName); replaceString.append('>'); StringBuffer displayString = new StringBuffer("</"); //$NON-NLS-1$ displayString.append(openElementName); displayString.append('>'); proposal = new AntCompletionProposal(replaceString.toString(), cursorPosition - prefix.length(), prefix.length(), replaceString.length(), null, displayString.toString(), AntEditorMessages.getString("AntEditorCompletionProcessor.39"), AntCompletionProposal.TAG_CLOSING_PROPOSAL); //$NON-NLS-1$ } } return proposal; } protected char getPreviousChar() { ITextSelection selection = (ITextSelection) viewer.getSelectionProvider().getSelection(); int offset = selection.getOffset(); char previousChar = '?'; try { previousChar = viewer.getDocument().getChar(offset - 1); } catch (BadLocationException e) { // do nothing } return previousChar; } /** * Returns the replacement string for the specified task name. */ private String getTaskProposalReplacementString(String aTaskName, boolean hasNested) { StringBuffer replacement = new StringBuffer("<"); //$NON-NLS-1$ replacement.append(aTaskName); ProposalNode task = getDescriptionProvider().getTaskNode(aTaskName); if (task != null) { appendRequiredAttributes(replacement, task); } if (hasNested) { replacement.append("></"); //$NON-NLS-1$ replacement.append(aTaskName); replacement.append('>'); } else { replacement.append("/>"); //$NON-NLS-1$ } return replacement.toString(); } private void appendRequiredAttributes(StringBuffer replacement, ProposalNode task) { if (task.nodes != null) { boolean requiredAdded = false; Entry<String, ProposalNode> entry = null; for (Iterator<Entry<String, ProposalNode>> i = task.nodes.entrySet().iterator(); i.hasNext();) { entry = i.next(); String name = entry.getKey(); ProposalNode att = entry.getValue(); if ("yes".equalsIgnoreCase(att.required)) { //$NON-NLS-1$ replacement.append(' '); replacement.append(name); replacement.append("=\"\""); //$NON-NLS-1$ if (!requiredAdded) { additionalProposalOffset = name.length() + 2; requiredAdded = true; } } } } } /** * Returns whether the named element supports nested elements. */ private boolean hasNestedElements(String elementName) { IElement element = getDtd().getElement(elementName); if (element != null) { return !element.isEmpty(); } AntTypeDefinition taskClass = getTaskClass(elementName); if (taskClass != null) { IntrospectionHelper helper = getIntrospectionHelper(taskClass); if (helper != null) { Enumeration<String> nested = helper.getNestedElements(); return nested.hasMoreElements(); } } return false; } /** * Finds a direct child element with <code>aChildElementName</code> of <code>anElement</code>. * <P> * The child will not be searched for in the whole hierarchy but only in the hierarchy step below. * * @return the found child or <code>null</code> if not found. */ protected Element findChildElementNamedOf(Element anElement, String aChildElementName) { NodeList nodeList = anElement.getChildNodes(); for (int i = 0; i < nodeList.getLength(); i++) { Node childNode = nodeList.item(i); if (childNode.getNodeType() == Node.ELEMENT_NODE) { if (childNode.getNodeName().equals(aChildElementName)) { return (Element) childNode; } } } return null; } /** * Determines the current prefix that should be used for completion. */ private String getCurrentPrefix() { if (currentPrefix != null) { return currentPrefix; } ITextSelection selection = (ITextSelection) viewer.getSelectionProvider().getSelection(); IDocument doc = viewer.getDocument(); return getPrefixFromDocument(doc.get(), selection.getOffset() + selection.getLength()).toLowerCase(); } /** * Returns the prefix in the specified document text with respect to the specified offset. * * @param aDocumentText * the whole content of the edited file as String * @param anOffset * the cursor position */ protected String getPrefixFromDocument(String aDocumentText, int anOffset) { if (currentPrefix != null) { return currentPrefix; } int startOfWordToken = anOffset; char token = 'a'; if (startOfWordToken > 0) { token = aDocumentText.charAt(startOfWordToken - 1); } while (startOfWordToken > 0 && (Character.isJavaIdentifierPart(token) || '.' == token || '-' == token || ';' == token) && !('$' == token)) { startOfWordToken--; if (startOfWordToken == 0) { break; // word goes right to the beginning of the doc } token = aDocumentText.charAt(startOfWordToken - 1); } if (startOfWordToken != anOffset) { currentPrefix = aDocumentText.substring(startOfWordToken, anOffset).toLowerCase(); } else { currentPrefix = IAntCoreConstants.EMPTY_STRING; } return currentPrefix; } /** * Returns the current proposal mode. */ protected int determineProposalMode(IDocument document, int aCursorPosition, String aPrefix) { if (currentProposalMode != -1) { return currentProposalMode; } if (document.getLength() == 0 || (document.getLength() == 1 && document.get().equals("<"))) { //$NON-NLS-1$ return PROPOSAL_MODE_BUILDFILE; } // String from beginning of document to the beginning of the prefix String text = document.get(); String stringToPrefix = text.substring(0, aCursorPosition - aPrefix.length()); if (stringToPrefix.length() == 0) { return PROPOSAL_MODE_BUILDFILE; } // Is trimmable from behind String trimmedString = stringToPrefix.trim(); if (antModel != null && antModel.getProjectNode() == null) { currentTaskString = getTaskStringFromDocumentStringToPrefix(trimmedString); if ("project".equals(currentTaskString)) { //$NON-NLS-1$ return PROPOSAL_MODE_ATTRIBUTE_PROPOSAL; } return PROPOSAL_MODE_BUILDFILE; } char lastChar = 0; if (trimmedString.length() > 0) { lastChar = trimmedString.charAt(trimmedString.length() - 1); } else { return PROPOSAL_MODE_TASK_PROPOSAL; } if (stringToPrefix.charAt(stringToPrefix.length() - 1) != lastChar && lastChar != '>' && lastChar != ',') { /* * Substring must be trimmable from behind in case of attribute proposal because a space or a new line must be used as delimiter between * task name and attribute or attribute and attribute. Example: '<property id="bla" name="hups"' */ // Attribute proposal if (lastChar != '>' && lastChar != '<') { currentTaskString = getTaskStringFromDocumentStringToPrefix(trimmedString); if (currentTaskString != null && isKnownElement(currentTaskString)) { return PROPOSAL_MODE_ATTRIBUTE_PROPOSAL; } } } else if (stringToPrefix.charAt(stringToPrefix.length() - 1) == '"' || trimmedString.charAt(trimmedString.length() - 1) == ',') { // Attribute value proposal currentTaskString = getTaskStringFromDocumentStringToPrefix(trimmedString); if (currentTaskString != null && isKnownElement(currentTaskString)) { return PROPOSAL_MODE_ATTRIBUTE_VALUE_PROPOSAL; } } else { // Possibly a Task proposal int spaceIndex = stringToPrefix.lastIndexOf(' '); int lessThanIndex = stringToPrefix.lastIndexOf('<'); int greaterThanIndex = stringToPrefix.lastIndexOf('>'); // Task proposal if (greaterThanIndex < lessThanIndex) { // we are inside an open element if (lastChar == '$') { return PROPOSAL_MODE_PROPERTY_PROPOSAL; } if (lessThanIndex > spaceIndex) { int slashIndex = stringToPrefix.lastIndexOf('/'); if (slashIndex == lessThanIndex + 1) { return PROPOSAL_MODE_TASK_PROPOSAL_CLOSING; // ... </ } return PROPOSAL_MODE_TASK_PROPOSAL; } } if (lessThanIndex < greaterThanIndex) { if (isPropertyProposalMode(stringToPrefix)) { return PROPOSAL_MODE_PROPERTY_PROPOSAL; } return PROPOSAL_MODE_TASK_PROPOSAL; } } // Property proposal if (isPropertyProposalMode(stringToPrefix)) { return PROPOSAL_MODE_PROPERTY_PROPOSAL; } return PROPOSAL_MODE_NONE; } private boolean isPropertyProposalMode(String stringToPrefix) { if (stringToPrefix.length() >= 2) { String lastTwoChars = stringToPrefix.substring(stringToPrefix.length() - 2, stringToPrefix.length()); if (lastTwoChars.equals("${") || //$NON-NLS-1$ stringToPrefix.charAt(stringToPrefix.length() - 1) == '$') { return true; } } return false; } /** * Returns the last occurring task string in the specified string. * <P> * The returned string must not necessarily be a valid Ant task string. This can be tested with the method <code>inNamedTaskKnown(String)</code> * after invoking this method. * * @param aDocumentStringToPrefix * the String that contains the whole string of the currently edited file from the beginning up to the prefix for code completion. * Example: '<project default="name"><property '. * * @return the extracted task string or <code>null</code> if no string could be extracted. */ private String getTaskStringFromDocumentStringToPrefix(String aDocumentStringToPrefix) { int lessThanIndex = aDocumentStringToPrefix.lastIndexOf('<'); if (lessThanIndex > -1) { String taskString = aDocumentStringToPrefix.trim(); taskString = taskString.substring(lessThanIndex + 1, taskString.length()); int index = taskString.indexOf(' '); if (index > 0) { taskString = taskString.substring(0, index); } index = taskString.indexOf('\n'); if (index > 0) { taskString = taskString.substring(0, index); } index = taskString.indexOf('\r'); if (index > 0) { taskString = taskString.substring(0, index); } return taskString; } return null; } /** * Returns the last occurring attribute string in the specified string. <code>null</code> is returned if no attribute string is available. * <P> * Calling this method is only safe if the current proposal mode is really <code>PROPOSAL_MODE_ATTRIBUTE_VALUE_PROPOSAL</code>. */ public static String getAttributeStringFromDocumentStringToPrefix(String docStringToPrefix) { int index = docStringToPrefix.lastIndexOf('='); if (index == -1) { return null; } String subString = docStringToPrefix.substring(0, index); subString = subString.trim(); index = subString.lastIndexOf(' '); if (index > 0) { subString = subString.substring(index + 1, subString.length()); } index = subString.lastIndexOf('\n'); if (index > 0) { subString = subString.substring(index + 1, subString.length()); } index = subString.lastIndexOf('\r'); if (index > 0) { subString = subString.substring(index + 1, subString.length()); } return trimBeginning(subString); } private static String trimBeginning(String toBeTrimmed) { int i = 0; while ((i != toBeTrimmed.length()) && Character.isWhitespace(toBeTrimmed.charAt(i))) { i++; } return toBeTrimmed.substring(i); } /** * Returns whether the specified element name is known */ protected boolean isKnownElement(String elementName) { if (elementName.equals("target") || elementName.equals("project") || elementName.equals("extension-point")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ return true; } AntProjectNode node = antModel.getProjectNode(); if (node != null) { Project antProject = node.getProject(); ComponentHelper helper = ComponentHelper.getComponentHelper(antProject); if (helper.getDefinition(elementName) != null) { return true; } if (helper.getDefinition(antModel.getNamespaceCorrectName(elementName)) != null) { return true; } // not everything is a task or type (nested elements) if (getDtd().getElement(elementName) != null) { return true; } if (getNestedType() != null) { return true; } } return false; } private AntTypeDefinition getTaskClass(String taskName) { AntTypeDefinition clss = null; AntProjectNode node = antModel.getProjectNode(); if (node != null) { Project antProject = node.getProject(); Map<String, AntTypeDefinition> tasksAndTypes = ComponentHelper.getComponentHelper(antProject).getAntTypeTable(); clss = tasksAndTypes.get(taskName); if (clss == null) { clss = tasksAndTypes.get(antModel.getNamespaceCorrectName(taskName)); } } return clss; } /** * Finds the parent task element in respect to the cursor position. * * @return the parent task element or <code>null</code> if not found. */ protected String getParentName(IDocument document, int aLineNumber, int aColumnNumber) { if (document.getLength() == 0) { return null; } AntProjectNode project = antModel.getProjectNode(); if (project == null) { return null; } int offset = getOffset(document, aLineNumber, aColumnNumber); if (offset == -1) { return null; } IAntElement node = project.getNode(offset); if (node == null) { node = antModel.getOpenElement(); } if (node == null) { return IAntCoreConstants.EMPTY_STRING; } else if (node instanceof AntTaskNode) { String name = node.getName(); if (offset <= node.getOffset() + name.length() - 1) { // not really the enclosing node as the offset is within the name of the node node = node.getParentNode(); } else { return name; } } if (node instanceof AntTaskNode) { return node.getName(); } else if (node instanceof AntTargetNode) { return "target"; //$NON-NLS-1$ } else { return "project"; //$NON-NLS-1$ } } /** * Return the properties as defined in the entire buildfile * * @return a map with all the found properties */ private Map<String, Object> findPropertiesFromDocument() { Project project = antModel.getProjectNode().getProject(); return project.getProperties(); } private Map<String, Target> getTargets() { Project project = antModel.getProjectNode().getProject(); return project.getTargets(); } protected File getEditedFile() { IWorkbenchPage page = AntUIPlugin.getActivePage(); if (page == null) { return null; } IEditorPart editor = page.getActiveEditor(); if (editor == null) { return null; } FileEditorInput input = (FileEditorInput) editor.getEditorInput(); String projectPath = input.getFile().getProject().getLocation().toFile().getAbsolutePath(); String projectRelativeFilePath = input.getFile().getFullPath().removeFirstSegments(1).makeRelative().toString(); return new File(projectPath + File.separator + projectRelativeFilePath); } private String getOpenElementName() { AntElementNode node = antModel.getOpenElement(); if (node == null) { return null; } return node.getName(); } /** * Finds the enclosing target in respect to the cursor position and returns its name * * @return the name of the enclosing target or <code>null</code> if not found or the element is not contained in a target. */ private String getEnclosingTargetName(IDocument document, int aLineNumber, int aColumnNumber) { AntProjectNode project = antModel.getProjectNode(); int offset = getOffset(document, aLineNumber, aColumnNumber); if (offset == -1) { return null; } IAntElement node = project.getNode(offset); if (node instanceof AntTaskNode) { node = node.getParentNode(); if (!(node instanceof AntTargetNode)) { // top level task node = null; } } else if (node instanceof AntProjectNode) { node = null; } String targetName = null; if (node == null || (targetName = ((AntTargetNode) node).getTarget().getName()) == null || targetName.length() == 0) { return null; } return targetName; } private int getOffset(IDocument document, int line, int column) { try { return document.getLineOffset(line) + column - 1; } catch (BadLocationException e) { return -1; } } /** * Sets this processor's set of characters triggering the activation of the completion proposal computation. * * @param activationSet * the activation set */ public void setCompletionProposalAutoActivationCharacters(char[] activationSet) { autoActivationChars = activationSet; } /* * (non-Javadoc) * * @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#extractPrefix(org.eclipse.jface.text.ITextViewer, int) */ @Override protected String extractPrefix(ITextViewer textViewer, int offset) { return getPrefixFromDocument(textViewer.getDocument().get(), offset); } /** * Cut out angular brackets for relevance sorting, since the template name does not contain the brackets. */ @Override protected int getRelevance(Template template, String prefix) { String newprefix = prefix; if (newprefix.startsWith("<")) {//$NON-NLS-1$ newprefix = prefix.substring(1); } if (template.getName().startsWith(newprefix)) { return 90; } return 0; } /* * (non-Javadoc) * * @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#getTemplates(java.lang.String) */ @Override protected Template[] getTemplates(String contextTypeId) { return AntTemplateAccess.getDefault().getTemplateStore().getTemplates(contextTypeId); } /* * (non-Javadoc) * * @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#getContextType(org.eclipse.jface.text.ITextViewer, * org.eclipse.jface.text.IRegion) */ @Override protected TemplateContextType getContextType(ITextViewer textViewer, IRegion region) { switch (determineProposalMode(textViewer.getDocument(), cursorPosition, getCurrentPrefix())) { case PROPOSAL_MODE_TASK_PROPOSAL: if (getEnclosingTargetName(textViewer.getDocument(), lineNumber, columnNumber) == null) { return AntTemplateAccess.getDefault().getContextTypeRegistry().getContextType(TargetContextType.TARGET_CONTEXT_TYPE); } return AntTemplateAccess.getDefault().getContextTypeRegistry().getContextType(TaskContextType.TASK_CONTEXT_TYPE); case PROPOSAL_MODE_BUILDFILE: return AntTemplateAccess.getDefault().getContextTypeRegistry().getContextType(BuildFileContextType.BUILDFILE_CONTEXT_TYPE); case PROPOSAL_MODE_NONE: case PROPOSAL_MODE_ATTRIBUTE_PROPOSAL: case PROPOSAL_MODE_TASK_PROPOSAL_CLOSING: case PROPOSAL_MODE_ATTRIBUTE_VALUE_PROPOSAL: case PROPOSAL_MODE_PROPERTY_PROPOSAL: case PROPOSAL_MODE_NESTED_ELEMENT_PROPOSAL: default: return null; } } /* * (non-Javadoc) * * @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#getImage(org.eclipse.jface.text.templates.Template) */ @Override protected Image getImage(Template template) { return AntUIImages.getImage(IAntUIConstants.IMG_TEMPLATE_PROPOSAL); } /* * (non-Javadoc) * * @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#createContext(org.eclipse.jface.text.ITextViewer, * org.eclipse.jface.text.IRegion) */ @Override protected TemplateContext createContext(ITextViewer contextViewer, IRegion region) { TemplateContextType contextType = getContextType(contextViewer, region); if (contextType != null) { Point selection = contextViewer.getSelectedRange(); Position position; if (selection.y > 0) { position = new Position(selection.x, selection.y); } else { position = new Position(region.getOffset(), region.getLength()); } IDocument document = contextViewer.getDocument(); return new AntContext(contextType, document, antModel, position); } return null; } /* * (non-Javadoc) * * @see org.eclipse.jface.text.templates.TemplateCompletionProcessor#createProposal(org.eclipse.jface.text.templates.Template, * org.eclipse.jface.text.templates.TemplateContext, org.eclipse.jface.text.Region, int) */ @Override protected ICompletionProposal createProposal(Template template, TemplateContext context, IRegion region, int relevance) { AntTemplateProposal proposal = new AntTemplateProposal(template, context, region, getImage(template), relevance); proposal.setInformationControlCreator(new AntTemplateInformationControlCreator()); return proposal; } protected ISchema getDtd() { if (fgDtd == null) { IRunnableWithProgress runnable = new IRunnableWithProgress() { @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { try { fgDtd = parseDtd(); } catch (IOException e) { AntUIPlugin.log(e); } catch (ParseError e) { AntUIPlugin.log(e); } } }; IProgressService service = PlatformUI.getWorkbench().getProgressService(); try { service.busyCursorWhile(runnable); } catch (InvocationTargetException e) { // do nothing } catch (InterruptedException e) { // do nothing } } return fgDtd; } /** * The provider for all task and attribute descriptions. */ private TaskDescriptionProvider getDescriptionProvider() { return TaskDescriptionProvider.getDefault(); } protected static void resetCodeCompletionDataStructures() { fgDtd = null; TaskDescriptionProvider.reset(); } /* * (non-Javadoc) * * @see org.eclipse.jface.text.contentassist.ICompletionListener#assistSessionStarted(org.eclipse.jface.text.contentassist.ContentAssistEvent) */ @Override public void assistSessionStarted(ContentAssistEvent event) { IContentAssistant assistant = event.assistant; if (assistant instanceof IContentAssistantExtension2) { fContentAssistant = (IContentAssistantExtension2) assistant; } } /* * (non-Javadoc) * * @see org.eclipse.jface.text.contentassist.ICompletionListener#assistSessionEnded(org.eclipse.jface.text.contentassist.ContentAssistEvent) */ @Override public void assistSessionEnded(ContentAssistEvent event) { fContentAssistant = null; fTemplatesOnly = false; } /* * (non-Javadoc) * * @see org.eclipse.jface.text.contentassist.ICompletionListener#selectionChanged(org.eclipse.jface.text.contentassist.ICompletionProposal, * boolean) */ @Override public void selectionChanged(ICompletionProposal proposal, boolean smartToggle) { // do nothing } private String getIterationGestureMessage(String showMessage) { final IBindingService bindingSvc = PlatformUI.getWorkbench().getAdapter(IBindingService.class); TriggerSequence[] triggers = bindingSvc.getActiveBindingsFor(getContentAssistCommand()); String message; if (triggers.length > 0) { message = MessageFormat.format(AntEditorMessages.getString("AntEditorCompletionProcessor.63"), new Object[] { triggers[0].format(), showMessage }); //$NON-NLS-1$ } else { message = MessageFormat.format(AntEditorMessages.getString("AntEditorCompletionProcessor.64"), new Object[] { showMessage }); //$NON-NLS-1$ } return message; } private ParameterizedCommand getContentAssistCommand() { final ICommandService commandSvc = PlatformUI.getWorkbench().getAdapter(ICommandService.class); final Command command = commandSvc.getCommand(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS); ParameterizedCommand pCmd = new ParameterizedCommand(command, null); return pCmd; } }